<template>
    <RenderForm :key="`${new Date().getTime()}`"/>
</template>
<script lang="jsx" setup key="v3Form">
    /* 
    * 基础表单组件，支持button/input/radio/checkbox/switch/datetime/upload/select/autocomplete/selectTree，
    * 对外提供submit的监听暴露表单数据（也可自定义）、支持表单校验、表单重置、表单校验规则配置等
    * */
    import { ref, useSlots, reactive, watchEffect, onMounted } from 'vue'
    import { ElMessage } from 'element-plus'
    import datePicker from './datePicker.vue'
    import fileUpload from './fileUpload.vue'
    import imgUpload from './imgUpload.vue'
    import asyncSelect from './asyncSelect.vue'
    import v3SelectTree from './selectTree.vue'
    import v3Button from './v3Button.vue'
    import { factory } from '@/utils/factory'
    import { trim, isBoolean, isEmpty, isNumber, isArray, isNotEmpty, isFunction, flattenToNested, flattenObject } from '@/utils/tools'
    import { useFetch } from '@/hooks/fetch'
    const slots = useSlots();
    const emit = defineEmits(['submit', 'fetch', 'event']);
    const props = defineProps({
        schema: {
            type: Object,
            default: () => {}
        },
        // 表单数据对象
        model: {
            type: Object,
            default: () => {}
        },
        /* 单独使用时，表单结构数据 */
        items: {
            type: Array,
            default: () => []
        },
        notRule: {
            type: Boolean,
            default: false
        },
        // 是否独立使用,默认为true、query/dialog 组件已传递 isAlone 为false
        isAlone: {
          type: Boolean,
          default: true
        },
        itemSlotIdx: {
            type: [Number, Array],
            default: 0
        }
    })
    const { itemSlotIdx, items, isAlone, notRule } = props
    
    let schema = {
        align: 'center',
        labelPosition: 'right',
        labelWidth: "80px",
        ...props.schema
    }
    // console.log('v3Form--schema--', schema)
    /* 单独使用时 */
    if(isAlone) schema.items = items
    // 表单的数据
    let formData = reactive({
        _idx: schema.index,
    })
    // 表单需提交数据-- 收集formData中的reqField、prop字段，抛弃冗余字段，少于或等于formData中的数据
    let submitData = reactive({})
    const childFormData = {}
    const widgetData = {} // 组件数据
    const v3FormRef = ref() // form表单ref
    const btnLoading = reactive({}) // 按钮ref
    /* form 私有方法 */
    const privateFn = {
        reset: () => {
            const ref = v3FormRef.value
            if (!ref) return
            ref.resetFields()
            Object.keys(submitData).forEach(item => {
                submitData[item] = ''
            })
            Object.keys(formData).forEach(item => {
                formData[item] = ''
            })
            Object.keys(childFormData).forEach(item => {
                childFormData[item] = ''
            })
            Object.keys(btnLoading).forEach(item => {
                btnLoading[item] = false
            })
            emit('submit', {}, ref, null)
            // console.log('v3Form--reset--', btnLoading)
        },
        getFormData: () => {
            submitData = {}
            schema.items.forEach(item => {
                const { config = undefined, prop } = item
                const { reqField = undefined } = config || {}
                /* reqField为配置参数：name转换为id的情况 */
                 submitData[reqField || prop] = formData[reqField] || formData[prop]
                 isEmpty(submitData[reqField]) && delete submitData[reqField]
                 isEmpty(submitData[prop]) && delete submitData[prop]
            })
            // console.log('v3Form--getFormData--', submitData, formData)
            if (props.model) {
                Object.keys(props.model).forEach(item => {
                    if (formData[item]) {
                        submitData[item] = formData[item]
                    } 
                })
            }
            // console.log('v3Form--getFormData--', submitData, formData)
            return submitData
        },
        setBtnStatus: (btn) => {
            if (!isBoolean(btnLoading[btn.rangeFn])) return
            const bool = btnLoading[btn.rangeFn]
            btnLoading[btn.rangeFn] = !bool
            // console.log('v3Form--setBtnStatus--', btn, btnLoading[btn.rangeFn])
        }
    }
    const submit = async config => {
        const { rangeFn, actOn, successCb, hookFn, isCustom, method } = config
        if (rangeFn === 'reset') {
            return privateFn.reset()
        }
        const fData = privateFn.getFormData()
        const formRef = v3FormRef.value
        if (!formRef) return
        // 根据formData修改prop.data的额外数据
        const data = filterData(props.model, formData)
        // 修正值，"ture"为true，"false"为false", 
        const params = factory.correctParams({...data, ...fData}) 
        // 将 {'a.b': 1} 转换为 {a: {b: 1}}
        const result = flattenToNested(params)
        console.log(`v3Form-submit--${actOn}-${rangeFn}--`, result)
        /* 独立使用时，直接提交至对应方法 */
        /* if (config.method && isAlone) {
            formRef.validate(valid => valid ? config.method(result, config, formRef) : false)
        } */
        /* 如果插件无法满足需求，可使用自定义方法 */
        if (isCustom || hookFn) {
            // console.log('handleEvent----', config)
            emit('event', {"widgetName": 'form', ...config, data: result})
        } else if (method) {
            /* 直接提交至对应方法 */
            formRef.validate(valid => valid ? method(result, config, formRef) : false)
        } else if (privateFn[rangeFn] && (actOn === 'query' || actOn === 'form' || actOn === 'dialog')) { /* 如果有私有方法 */
            privateFn[rangeFn](config, result)
        } else {
            /* 点击取消时，无需验证，直接提交至上层处理 */
            if (rangeFn === 'close' && actOn === 'dialog'){
                return emit('submit', config, result, successCb)
            } else {
                await formRef.validate(valid => valid ? emit('submit', config, result, successCb) : false)
            }
        }
        console.log('submit-----', formData, submitData)
    }
    const filterData = (data1 = {}, data) => {
        let obj = {}
        Object.keys(data1).forEach(key => {
            if (data[key] || !(key in data)) {
                obj[key] = data[key] || data1[key]
            }
        })
        return obj
    }
    const querySearchAsync = async (queryString, cb, prop, obj) => {
        if (!widgetData[prop]?.length) {
            const { fieldKey = undefined, fieldValue = undefined, reqField = undefined } = obj || {}
            const val = [fieldValue || 'value']
            const key = [fieldKey || 'key']
            const { body, error } = await useFetch(obj)
            /* error 默认 undefined */
            if (error) {
                isNotEmpty(error) && emit('fetch', {"widgetName": 'query', error})
                return error
            }
            widgetData[prop] = body?.length && body.map(item => {
                return {
                    "value": item[val],
                    "key": item[key]
                }
            }) || []
        }
        var results = queryString ? widgetData[prop].filter(createStateFilter(queryString, obj)) : widgetData[prop];
        cb(results)
    }
    const createStateFilter = (queryString, obj) => {
        return state => {
          return state['value'].toLowerCase().includes(queryString.toLowerCase())
        };
    }
    const handleSelect = (val, obj) => {
        // console.log('handleSelect', val, obj)
        /* 处理下拉数据，当配置了reqField时，将value值赋给reqField，key赋值给prop，否则将value值赋给prop */
        const { prop, config } = obj
        if (config?.reqField) {
            formData[config.reqField] = val
            if (config.enums && config.reqVal === 'value') {
                formData[prop] = config.enums[val]
            }
        } else {
            formData[prop] = val
        }   
    }
    const handleFetch = error => {
        emit('fetch', {"widgetName": 'form', error})
    }
    const handleUpload = (val, obj) => {
        const { prop, config } = obj
        formData[prop]= val
        // console.log('handleUpload', val, obj, formData)
    }
    const handleDatePick = (val, obj) => {
        // console.log('handleDatePick', val)
        const { prop, config } = obj
        formData[prop] = val
    }
    const handleAutocomplete = (obj, prop, { config }) => {
        if (config?.reqField) formData[`${config.reqField}`] = obj['key']
    }
    const handleSlotData = (arr = schema.items) => {
        /* 如果有插槽 */
      if (itemSlotIdx || itemSlotIdx?.length) {
        Object.keys(props.model).forEach((key, i) => {
            if ((!(key in formData)) && !formData[key]) {
                formData[key] = props.model[key]
            }
        })
      }
      
    }
    /* 处理不同类型标签的默认值 */
    const tagEnum = {
        'checkbox': [],
        'switch': false
    }
    const init = (data = {}) => {
        /* 先处理默认/下拉数据（接口请求、枚举转化）或其他特殊情况 */
        const arr = schema.items
        // console.log('v3Form--init--', arr, data)
        // 如果数据中字段包含x.xx.xxx
        data = flattenObject(data)
        arr.length && arr.forEach(async item => {
            const { prop, tag = 'input', slot, config, showLogic } = item
            const { fieldKey = undefined, fieldValue = undefined, reqField = undefined } = config || {}
            const defaultValue = data[prop] || item.defaultValue || tagEnum[tag]
            const val = [fieldValue || 'value']
            const key = [fieldKey || 'key']
            if (!config) item.config = {}
            if (slot && !prop) item.prop = slot
            if (prop) {
                let obj = {[prop]: defaultValue ?? '' }
                if (reqField) {
                    obj = {...obj, [reqField]: ''}
                }
                /* 处理日期类型 */
                if ((tag === 'date' || tag === 'daterange' || tag === 'month' || tag === 'datetimerange') && isNumber(obj[prop]) && item.auxFunc) {
                    obj[prop] = factory[item.auxFunc](obj[prop])
                }
                /* 处理特殊需求，用方法给 porp 赋值 */
                if (item.method) {
                    if(isFunction(item.method)) obj[prop] = item.method(obj, factory)
                }
                /* 处理转译：‘|’前属性获取到的数据，需要‘|’的函数处理再赋值给formData */
                if (prop.indexOf('|') > -1) {
                    const [pr, fnName] = prop.split('|')
                    const fn = trim(fnName)
                    const p = trim(pr)
                    if (!factory[fn] || !fn) throw new Error(`${fn} 方法不存在！或未配置方法名！`)
                    formData[p] = factory[fn](data[p])
                    item['prop'] = p
                    item["auxFunc"] = fn
                }
                Object.assign(formData, obj)
            }
            // if (config?.reqFiled) formData[config?.reqFiled] = ''
            if (config?.enums) {
                const enumKeys = Object.keys(config.enums)
                widgetData[prop] = enumKeys.map(key => {
                    const value = config.enums[key]
                    if (defaultValue && reqField && defaultValue === value) formData[reqField] = key
                    return {
                        "value": value,
                        "key": key
                    }
                })
                
            }
            /* 有指定数据时，处理defaultValue可能为key的情况 */
            if ((defaultValue && !Array.isArray(defaultValue)) && widgetData[prop]?.length) {
                !widgetData[prop].filter(item => item[key] === defaultValue || item[val] === defaultValue).length && ElMessage.warning('注意！默认值不在枚举对象或接口数据内！')
                widgetData[prop].forEach(el => {
                    if (el[key] === defaultValue) {
                        formData[`${reqField || `${prop}Key`}`] = defaultValue
                    }
                })
            }
            
            item.config.data = widgetData[prop] || {}
            /* showLogic：根据页面断言信息(传入的数据)判断，queryLogic根据当前form表单数据判断 */
            // item.isShow = factory.validateByEval(showLogic, pageInfo._assert_) || showLogic
        })
        schema.operations?.length && schema.operations.forEach(item => {
            btnLoading[item.rangeFn] = item.btnLoading
        })
        if (!slots.formItem) return
        /* 处理插槽项的排列 */
        if (isArray(itemSlotIdx)) {
            itemSlotIdx.forEach((i) => {
                !arr.filter(el => el.tag === 'slot' && el.idx === i && el.isShow).length && arr.splice(i, 0, {tag: 'slot', idx: i, isShow: true})
          })
        } else if (itemSlotIdx){
            arr.splice(itemSlotIdx, 0, {tag: 'slot', idx: itemSlotIdx, isShow: true})
        }
    }
    watchEffect(() => {
        if (props.model) {
            /* 处理插槽情况的数据 */
            handleSlotData()
        }
    })
    onMounted(() => {
        init(props.model)
    })
    
    /* 对外暴露的方法 */
    defineExpose({ "getFormData": privateFn.getFormData, "setBtnStatus": privateFn.setBtnStatus, "reset": privateFn.reset })
    /* 渲染函数jsx */
    const RenderForm = () =>
        <el-form ref={v3FormRef} inline={schema.inline} model={formData} class={'elForm'} label-width={schema.labelWidth} label-position={schema.labelPosition}>
            {
                schema.items.map((item, i) => {
                    const { config = {}, tag = 'input', prop, label, className, isShow = true, align, button, labelWidth, showLogic, relyLogic } = item;
                    const { 
                        clearable = undefined,
                        disabled = undefined,
                        placeholder = undefined,
                        filterable = undefined,
                        min = undefined,
                        max = undefined,
                        maxlength = undefined,
                        readonly = undefined,
                        size = undefined,
                        prefixIcon = undefined,
                        suffixIcon = undefined,
                        legalExt = undefined,
                        multiple = undefined,
                        inputStyle = undefined,
                        width = undefined,
                        style = undefined,
                        showWordLimit = undefined,
                    } = config;
                    const rules = config.rules && !notRule ? config.rules : undefined;
                    const btnConfig = {
                        ...button
                    }
                    if (tag === 'child') {
                        return (
                        <el-form class="el-form-item clearfix" inline={schema.inline} model={childFormData}>
                            {slots.prop && slots.prop({ formData, item, index: i })}
                        </el-form>
                        );
                    } else if (prop === 'value') {
                        return null;
                    }
                    if (tag === 'slot' && item.idx === i) {
                        const obj = formData
                        if (isArray(itemSlotIdx)) {
                            const idx = itemSlotIdx.findIndex(n => n == [item.idx])
                            // console.log('slots---formItem', slots.formItem(obj), idx)
                            const formItem = slots?.formItem ? isAlone ? slots.formItem(obj)[idx] : slots?.formItem(obj)[0].children[0].children[idx] : ''
                            // console.log('formItem', formItem, obj)
                            return formItem ? <formItem item={obj} style={[{"min-width": "280px", 'width': width, "max-width": "600px"}, style]}></formItem> : null
                        } else {
                            return slots?.formItem && <slots.formItem item={obj} style={[{"min-width": "280px", 'width': width, "max-width": "600px"}, style]}></slots.formItem>
                        }
                    }
                    const isRender = factory.validateByEval(showLogic, formData) && isShow;
                    if (!isRender || !tag) return null;
                    return <el-form-item key={new Date()} label={label} label-width={labelWidth} rules={rules} prop={prop} class={className} align={align}
                            v-show={factory.validateByEval(relyLogic, formData)}
                            style={[{"min-width": "280px", 'width': width, "max-width": "600px"}, style]}>
                        {
                            tag === 'input' || tag === 'password' || tag === 'textarea' ? 
                                <el-input
                                    v-model={formData[prop]}
                                    placeholder={placeholder || '请输入'}
                                    clearable={clearable}
                                    maxlength={maxlength}
                                    type={tag}
                                    show-password={tag === 'password'}
                                    disabled={disabled}
                                    readonly={readonly}
                                    prefix-icon={prefixIcon}
                                    suffix-icon={suffixIcon}
                                    size={size}
                                    input-style={inputStyle}
                                    show-word-limit={showWordLimit}>
                                </el-input>
                            : tag === 'autocomplete' ?
                                <el-autocomplete
                                    v-model={formData[`${prop}`]}
                                    clearable={clearable}
                                    disabled={disabled}
                                    placeholder={placeholder || '请选择'}
                                    fetch-suggestions={(qs, cb) => querySearchAsync(qs, cb, prop, config)}
                                    // onSelect={obj => handleAutocomplete(obj, prop, item)}
                                >
                                    {{
                                        default: ({item}) => item['value']
                                    }}
                                </el-autocomplete>
                            : tag === 'fileUpload' ? 
                                <fileUpload v-model={formData[prop]} onChange={(val) => handleUpload(val, item)} config={config} legalExt={legalExt} />
                            : tag === 'imgUpload' ?
                                <imgUpload v-model={formData[prop]} legalExt={legalExt} />
                            : tag === 'switch' ?
                                <el-switch v-model={formData[prop]} />
                            : tag === 'radio' ?
                                <el-radio-group v-model={formData[prop]}>
                                {
                                    widgetData[prop]?.length && widgetData[prop].map(el => {
                                        return <el-radio label={el['key']} key={el['value']}>{el['value']}</el-radio>
                                    })
                                }
                                </el-radio-group>
                            : tag === 'checkbox' ?
                                <el-checkbox-group v-model={formData[prop]} min={min} max={max}>
                                {
                                    widgetData[prop]?.length && widgetData[prop].map(el => {
                                        return <el-checkbox label={el['value']} key={el['value']} value={el['value']}>{el['value']}</el-checkbox>
                                    })
                                }
                                </el-checkbox-group>
                            : tag === 'asyncSelect' ? // config配置source的形式，有bug
                                <asyncSelect
                                    v-model={formData[prop]}
                                    config={config}
                                    onFetch={obj => handleFetch(obj)}
                                    onEvent={(val) => handleSelect(val, item)}>
                                </asyncSelect>
                            : tag === 'select' ?
                                <el-select
                                    v-model={formData[prop]}
                                    filterable={filterable}
                                    clearable={clearable || true}
                                    multiple={multiple}
                                    onChange={(val) => handleSelect(val, item)}
                                    placeholder={placeholder || '请选择'}>
                                    {
                                        widgetData[prop]?.length && widgetData[prop].map(el => {
                                            return <el-option value={el['key']} label={el['value']} key={el['key']}></el-option>
                                        })
                                    }
                                </el-select>
                            : tag === 'tree' ?
                                <select-tree
                                    v-model={formData[prop]}
                                    treeData={regionTree}
                                    placeholder={placeholder || '请选择'}>
                                </select-tree>
                            : tag === 'button' ?
                                !item.operations
                                    ? <v3Button config={{...btnConfig, ...item }} onClick={() => submit(item) }></v3Button>
                                    : item.operations?.length && item.operations.map(el =>
                                        (el.isShow || isAlone) && <v3Button config={{...btnConfig, ...el }} onClick={() => submit(el) }>{{el}}</v3Button>
                                    )
                            : (tag === 'date' || tag === 'datetime' || tag === 'month' || tag === 'monthrange' || tag === 'datetimerange' || tag === 'daterange' || tag === 'year' || tag === 'yearrange' || tag === 'week' || tag === 'time') ?
                                <datePicker
                                    type={tag}
                                    v-model={formData[prop]}
                                    onChange={val => handleDatePick(val, item)}
                                    value-format={config?.valueFormat}
                                    placeholder={config?.placeholder}
                                    range-separator={config?.rangeSeparator}
                                    start-placeholder={config?.startPlaceholder}
                                    end-placeholder={config?.endPlaceholder}
                                    config={config}>
                                </datePicker>
                            : null
                        }
                    </el-form-item>
                })
            }
            {
                schema.operations?.length && <el-form-item>
                    {
                        schema.operations.map(item => {
                            const { rangeFn, isShow = true, button } = item
                            const btnConfig = {
                                loading: btnLoading[rangeFn] && item.request,
                                ...button
                            }
                            return isShow && <v3Button config={{...btnConfig, ...item}} onClick={() => submit(item)}/>
                        })
                    }
                </el-form-item>
            }
            {
            slots.default && slots.default(formData)
            }
        </el-form>
</script>
<style>
#app .elForm .el-input__wrapper {
  position: relative;
}
#app .elForm .el-input__wrapper .el-input__inner {
    padding-right: 18px;
}
#app .elForm .el-input__wrapper .el-input__suffix {
    position: absolute;
    right: 8px;
    top: 50%;
    transform: translateY(-50%);
}
</style>